Paths and straights#
This tutorial is based on PHIDL waveguides tutorial
You can create smooth paths in gdsfactory and extrude them with arbitrary cross-sections
Lets create a path:
Create a blank
PathAppend points to the
Patheither using the built-in functions (arc(),straight(),euler(), etc) or by providing your own lists of pointsSpecify what you want the cross-section (
CrossSection) to look likeCombine the
Pathand theCrossSection(will output a Device with the path polygons in it)
Path creation#
The first step is to generate the list of points we want the path to follow. Let’s start out by creating a blank Path and using the built-in functions to make a few smooth turns.
[1]:
import gdsfactory as gf
import numpy as np
import matplotlib.pyplot as plt
gf.CONF.plotter = 'holoviews'
P = gf.Path()
P.append(gf.path.arc(radius=10, angle=90)) # Circular arc
P.append(gf.path.straight(length=10)) # Straight section
P.append(gf.path.euler(radius=3, angle=-90)) # Euler bend (aka "racetrack" curve)
P.append(gf.path.straight(length=40))
P.append(gf.path.arc(radius=8, angle=-45))
P.append(gf.path.straight(length=10))
P.append(gf.path.arc(radius=8, angle=45))
P.append(gf.path.straight(length=10))
gf.plot(P)
2022-03-01 18:39:41.709 | INFO | gdsfactory.config:<module>:52 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 4.2.16
[2]:
p2 = P.copy().rotate()
gf.plot([P, p2])
[3]:
P.points - p2.points
[3]:
array([[ 0.00000000e+00, 0.00000000e+00],
[ 2.59744785e-02, -6.19378670e-02],
[ 5.24914793e-02, -1.23645416e-01],
...,
[ 3.00861576e+01, -4.76510048e+01],
[ 3.01066146e+01, -4.77010155e+01],
[ 3.30355468e+01, -5.47720833e+01]])
We can also modify our Path in the same ways as any other PHIDL object:
Manipulation with
move(),rotate(),mirror(), etcAccessing properties like
xmin,y,center,bbox, etc
[4]:
P.movey(10)
P.xmin = 20
gf.plot(P)
We can also check the length of the curve with the length() method:
[5]:
P.length()
[5]:
105.3428189759906
Defining the cross-section#
Now that we’ve got our path defined, the next step is to tell phidl what we want the cross-section of the path to look like. To do this, we create a blank CrossSection and add whatever cross-sections we want to it. We can then combine the Path and the CrossSection using the gf.path.extrude() function to generate our final geometry:
Option 1: Single layer and width cross-section#
The simplest option is to just set the cross-section to be a constant width by passing a number to extrude() like so:
[6]:
# Extrude the Path and the CrossSection
c = gf.path.extrude(P, layer=(1, 0), width=1.5)
c.plot()
[6]:
Option 2: Linearly-varying width#
A slightly more advanced version is to make the cross-section width vary linearly from start to finish by passing a 2-element list to extrude() like so:
[7]:
# Extrude the Path and the CrossSection
c = gf.path.extrude(P, layer=(1, 0), widths=(1, 3))
c.plot()
[7]:
Option 3: Arbitrary Cross-section#
You can also extrude an arbitrary cross_section
[8]:
X = gf.CrossSection()
# Add a a few "sections" to the cross-section
X.add(width=1, offset=0, layer=(1, 0), ports=("in", "out"))
X.add(width=2, offset=2, layer=(2, 0))
X.add(width=2, offset=-2, layer=(2, 0))
c = gf.path.extrude(P, cross_section=X)
c.plot()
[8]:
Now, what if we want a more complicated straight? For instance, in some photonic applications it’s helpful to have a shallow etch that appears on either side of the straight (often called a trench or sleeve). Additionally, it might be nice to have a Port on either end of the center section so we can snap other geometries to it. Let’s try adding something like that in:
[9]:
import gdsfactory as gf
p = gf.path.arc()
# Create a blank CrossSection
X = gf.CrossSection()
# Add a a few "sections" to the cross-section
X.add(width=1, offset=0, layer=(1, 0), ports=("in", "out"))
X.add(width=2, offset=2, layer=(2, 0))
X.add(width=2, offset=-2, layer=(2, 0))
# Combine the Path and the CrossSection
straight = gf.path.extrude(p, cross_section=X)
# Quickplot the resulting Component
straight.plot()
[9]:
Building Paths quickly#
You can pass append() lists of path segments. This makes it easy to combine paths very quickly. Below we show 3 examples using this functionality:
Example 1: Assemble a complex path by making a list of Paths and passing it to append()
[10]:
P = gf.Path()
# Create the basic Path components
left_turn = gf.path.euler(radius=4, angle=90)
right_turn = gf.path.euler(radius=4, angle=-90)
straight = gf.path.straight(length=10)
# Assemble a complex path by making list of Paths and passing it to `append()`
P.append(
[
straight,
left_turn,
straight,
right_turn,
straight,
straight,
right_turn,
left_turn,
straight,
]
)
gf.plot(P)
Example 2: Create an “S-turn” just by making a list of [left_turn, right_turn]
[11]:
P = gf.Path()
# Create an "S-turn" just by making a list
s_turn = [left_turn, right_turn]
P.append(s_turn)
gf.plot(P)
Example 3: Repeat the S-turn 3 times by nesting our S-turn list in another list
[12]:
P = gf.Path()
# Create an "S-turn" using a list
s_turn = [left_turn, right_turn]
# Repeat the S-turn 3 times by nesting our S-turn list 3x times in another list
triple_s_turn = [s_turn, s_turn, s_turn]
P.append(triple_s_turn)
gf.plot(P)
Note you can also use the Path() constructor to immediately contruct your Path:
[13]:
P = gf.Path([straight, left_turn, straight, right_turn, straight])
gf.plot(P)
Waypoint smooth paths#
You can also build smooth paths between waypoints with the smooth() function
[14]:
points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])
plt.plot(points[:, 0], points[:, 1], ".-")
plt.axis("equal")
[14]:
(17.5, 72.5, 8.5, 41.5)
[15]:
import gdsfactory as gf
import numpy as np
points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])
P = gf.path.smooth(
points=points,
radius=2,
bend=gf.path.euler, # Alternatively, use pp.arc
use_eff=False,
)
gf.plot(P)
Waypoint sharp paths#
It’s also possible to make more traditional angular paths (e.g. electrical wires) in a few different ways.
Example 1: Using a simple list of points
[16]:
P = gf.Path([(20, 10), (30, 10), (40, 30), (50, 30), (50, 20), (70, 20)])
gf.plot(P)
Example 2: Using the “turn and move” method, where you manipulate the end angle of the Path so that when you append points to it, they’re in the correct direction. Note: It is crucial that the number of points per straight section is set to 2 (``pp.straight(length, num_pts = 2)``) otherwise the extrusion algorithm will show defects.
[17]:
P = gf.Path()
P.append(gf.path.straight(length=10, npoints=2))
P.end_angle += 90 # "Turn" 90 deg (left)
P.append(gf.path.straight(length=10, npoints=2)) # "Walk" length of 10
P.end_angle += -135 # "Turn" -135 degrees (right)
P.append(gf.path.straight(length=15, npoints=2)) # "Walk" length of 10
P.end_angle = 0 # Force the direction to be 0 degrees
P.append(gf.path.straight(length=10, npoints=2)) # "Walk" length of 10
gf.plot(P)
[18]:
import gdsfactory as gf
X = gf.CrossSection()
X.add(width=1, offset=0, layer=(1, 0))
X.add(width=1.5, offset=2.5, layer=(2, 0))
X.add(width=1.5, offset=-2.5, layer=(3, 0))
component = gf.path.extrude(P, X)
component.show()
component.plot()
2022-03-01 18:39:46.425 | INFO | gdsfactory.show:show:36 - Klayout show extrude_a1b0e5b8: uid 6, ports [], aliases [], 3 polygons, 0 references
[18]:
Custom curves#
Now let’s have some fun and try to make a loop-de-loop structure with parallel straights and several Ports.
To create a new type of curve we simply make a function that produces an array of points. The best way to do that is to create a function which allows you to specify a large number of points along that curve – in the case below, the looploop() function outputs 1000 points along a looping path. Later, if we want reduce the number of points in our geometry we can trivially simplify the path.
[19]:
import numpy as np
def looploop(num_pts=1000):
"""Simple limacon looping curve"""
t = np.linspace(-np.pi, 0, num_pts)
r = 20 + 25 * np.sin(t)
x = r * np.cos(t)
y = r * np.sin(t)
points = np.array((x, y)).T
return points
# Create the path points
P = gf.Path()
P.append(gf.path.arc(radius=10, angle=90))
P.append(gf.path.straight())
P.append(gf.path.arc(radius=5, angle=-90))
P.append(looploop(num_pts=1000))
P.rotate(-45)
# Create the crosssection
X = gf.CrossSection()
X.add(width=0.5, offset=2, layer=1)
X.add(width=0.5, offset=4, layer=2)
X.add(width=1.5, offset=0, layer=3, ports=["in", "out"])
X.add(width=1, offset=0, layer=4)
c = gf.path.extrude(P, X)
c.show()
c.plot()
2022-03-01 18:39:46.696 | INFO | gdsfactory.show:show:36 - Klayout show extrude_5fa8e0d1: uid 8, ports ['in', 'out'], aliases [], 6 polygons, 0 references
[19]:
You can create Paths from any array of points – just be sure that they form smooth curves! If we examine our path P we can see that all we’ve simply created a long list of points:
[20]:
import numpy as np
path_points = P.points # Curve points are stored as a numpy array in P.points
print(np.shape(path_points)) # The shape of the array is Nx2
print(len(P)) # Equivalently, use len(P) to see how many points are inside
(1359, 2)
1359
Simplifying / reducing point usage#
One of the chief concerns of generating smooth curves is that too many points are generated, inflating file sizes and making boolean operations computationally expensive. Fortunately, PHIDL has a fast implementation of the Ramer-Douglas–Peucker algorithm that lets you reduce the number of points in a curve without changing its shape. All that needs to be done is when you made a component component() extruding
the path with a cross_section, you specify the simplify argument.
If we specify simplify = 1e-3, the number of points in the line drops from 12,000 to 4,000, and the remaining points form a line that is identical to within 1e-3 distance from the original (for the default 1 micron unit size, this corresponds to 1 nanometer resolution):
[21]:
# The remaining points form a identical line to within `1e-3` from the original
c = gf.path.extrude(p=P, cross_section=X, simplify=1e-3)
c.plot()
[21]:
Let’s say we need fewer points. We can increase the simplify tolerance by specifying simplify = 1e-1. This drops the number of points to ~400 points form a line that is identical to within 1e-1 distance from the original:
[22]:
c = gf.path.extrude(P, cross_section=X, simplify=1e-1)
c.plot()
[22]:
Taken to absurdity, what happens if we set simplify = 0.3? Once again, the ~200 remaining points form a line that is within 0.3 units from the original – but that line looks pretty bad.
[23]:
c = gf.path.extrude(P, cross_section=X, simplify=0.3)
c.plot()
[23]:
Curvature calculation#
The Path class has a curvature() method that computes the curvature K of your smooth path (K = 1/(radius of curvature)). This can be helpful for verifying that your curves transition smoothly such as in track-transition curves (also known as “Euler” bends in the photonics world). Euler bends have lower mode-missmatch loss as explained in this
paper
Note this curvature is numerically computed so areas where the curvature jumps instantaneously (such as between an arc and a straight segment) will be slightly interpolated, and sudden changes in point density along the curve can cause discontinuities.
[24]:
import matplotlib.pyplot as plt
import gdsfactory as gf
straight_points = 100
P = gf.Path()
P.append(
[
gf.path.straight(
length=10, npoints=straight_points
), # Should have a curvature of 0
gf.path.euler(
radius=3, angle=90, p=0.5, use_eff=False
), # Euler straight-to-bend transition with min. bend radius of 3 (max curvature of 1/3)
gf.path.straight(
length=10, npoints=straight_points
), # Should have a curvature of 0
gf.path.arc(radius=10, angle=90), # Should have a curvature of 1/10
gf.path.arc(radius=5, angle=-90), # Should have a curvature of -1/5
gf.path.straight(
length=2, npoints=straight_points
), # Should have a curvature of 0
]
)
gf.plot(P)
Arc paths are equivalent to bend_circular and euler paths are equivalent to bend_euler
[25]:
s, K = P.curvature()
plt.plot(s, K, ".-")
plt.xlabel("Position along curve (arc length)")
plt.ylabel("Curvature")
[25]:
Text(0, 0.5, 'Curvature')
[26]:
P = gf.path.euler(radius=3, angle=90, p=1.0, use_eff=False)
P.append(gf.path.euler(radius=3, angle=90, p=0.2, use_eff=False))
P.append(gf.path.euler(radius=3, angle=90, p=0.0, use_eff=False))
gf.plot(P)
[27]:
s, K = P.curvature()
plt.plot(s, K, ".-")
plt.xlabel("Position along curve (arc length)")
plt.ylabel("Curvature")
[27]:
Text(0, 0.5, 'Curvature')
You can compare two 90 degrees euler bend with 180 euler bend.
A 180 euler bend is shorter, and has less loss than two 90 degrees euler bend.
[28]:
import matplotlib.pyplot as plt
import gdsfactory as gf
straight_points = 100
P = gf.Path()
P.append(
[
gf.path.euler(radius=3, angle=90, p=1, use_eff=False),
gf.path.euler(radius=3, angle=90, p=1, use_eff=False),
gf.path.straight(length=6, npoints=100),
gf.path.euler(radius=3, angle=180, p=1, use_eff=False),
]
)
gf.plot(P)
[29]:
s, K = P.curvature()
plt.plot(s, K, ".-")
plt.xlabel("Position along curve (arc length)")
plt.ylabel("Curvature")
[29]:
Text(0, 0.5, 'Curvature')
Transitioning between cross-sections#
Often a critical element of building paths is being able to transition between cross-sections. You can use the transition() function to do exactly this: you simply feed it two CrossSections and it will output a new CrossSection that smoothly transitions between the two.
Let’s start off by creating two cross-sections we want to transition between. Note we give all the cross-sectional elements names by specifying the name argument in the add() function – this is important because the transition function will try to match names between the two input cross-sections, and any names not present in both inputs will be skipped.
[30]:
import numpy as np
import gdsfactory as gf
# Create our first CrossSection
X1 = gf.CrossSection()
X1.add(width=1.2, offset=0, layer=2, name="wg", ports=("o1", "o2"))
X1.add(width=2.2, offset=0, layer=3, name="etch")
X1.add(width=1.1, offset=3, layer=1, name="wg2")
# Create the second CrossSection that we want to transition to
X2 = gf.CrossSection()
X2.add(width=1, offset=0, layer=2, name="wg", ports=("o1", "o2"))
X2.add(width=3.5, offset=0, layer=3, name="etch")
X2.add(width=3, offset=5, layer=1, name="wg2")
# To show the cross-sections, let's create two Paths and
# create Devices by extruding them
P1 = gf.path.straight(length=5)
P2 = gf.path.straight(length=5)
wg1 = gf.path.extrude(P1, X1)
wg2 = gf.path.extrude(P2, X2)
# Place both cross-section Devices and quickplot them
c = gf.Component()
wg1ref = c << wg1
wg2ref = c << wg2
wg2ref.movex(7.5)
c.plot()
[30]:
Now let’s create the transitional CrossSection by calling transition() with these two CrossSections as input. If we want the width to vary as a smooth sinusoid between the sections, we can set width_type to 'sine' (alternatively we could also use 'linear').
[31]:
# Create the transitional CrossSection
Xtrans = gf.path.transition(cross_section1=X1, cross_section2=X2, width_type="sine")
# Create a Path for the transitional CrossSection to follow
P3 = gf.path.straight(length=15, npoints=100)
# Use the transitional CrossSection to create a Component
straight_transition = gf.path.extrude(P3, Xtrans)
straight_transition.show()
straight_transition.plot()
2022-03-01 18:39:49.788 | INFO | gdsfactory.show:show:36 - Klayout show extrude_d74e0984: uid 16, ports ['o1', 'o2'], aliases [], 5 polygons, 0 references
[31]:
[32]:
straight_transition.ports["o1"].cross_section.sections
[32]:
[{'width': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'offset': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'layer': 2,
'ports': ('o1', 'o2'),
'port_types': ('optical', 'optical'),
'hidden': False,
'name': 'wg'},
{'width': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'offset': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'layer': 3,
'ports': (None, None),
'port_types': ('optical', 'optical'),
'hidden': False,
'name': 'etch'},
{'width': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'offset': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'layer': 1,
'ports': (None, None),
'port_types': ('optical', 'optical'),
'hidden': False,
'name': 'wg2'}]
[33]:
straight_transition.ports["o2"].cross_section.sections
[33]:
[{'width': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'offset': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'layer': 2,
'ports': ('o1', 'o2'),
'port_types': ('optical', 'optical'),
'hidden': False,
'name': 'wg'},
{'width': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'offset': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'layer': 3,
'ports': (None, None),
'port_types': ('optical', 'optical'),
'hidden': False,
'name': 'etch'},
{'width': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'offset': <function gdsfactory.path._sinusoidal_transition.<locals>.sine(t)>,
'layer': 1,
'ports': (None, None),
'port_types': ('optical', 'optical'),
'hidden': False,
'name': 'wg2'}]
[34]:
wg1.plot()
[34]:
[35]:
wg2.plot()
[35]:
Now that we have all of our components, let’s connect() everything and see what it looks like
[36]:
c = gf.Component('transition_demo')
wg1ref = c << wg1
wgtref = c << straight_transition
wg2ref = c << wg2
wgtref.connect("o1", wg1ref.ports["o2"])
wg2ref.connect("o1", wgtref.ports["o2"])
c.show()
c.plot()
2022-03-01 18:39:50.850 | INFO | gdsfactory.show:show:36 - Klayout show transition_demo: uid 18, ports [], aliases [], 0 polygons, 3 references
[36]:
Note that since transition() outputs a CrossSection, we can make the transition follow an arbitrary path:
[37]:
# Transition along a curving Path
P4 = gf.path.euler(radius=25, angle=45, p=0.5, use_eff=False)
wg_trans = gf.path.extrude(P4, Xtrans)
c = gf.Component()
wg1_ref = c << wg1 # First cross-section Component
wg2_ref = c << wg2
wgt_ref = c << wg_trans
wgt_ref.connect("o1", wg1_ref.ports["o2"])
wg2_ref.connect("o1", wgt_ref.ports["o2"])
c.plot()
[37]:
Variable width / offset#
In some instances, you may want to vary the width or offset of the path’s cross- section as it travels. This can be accomplished by giving the CrossSection arguments that are functions or lists. Let’s say we wanted a width that varies sinusoidally along the length of the Path. To do this, we need to make a width function that is parameterized from 0 to 1: for an example function my_width_fun(t) where the width at t==0 is the width at the beginning of the Path and the width at
t==1 is the width at the end.
[38]:
def my_custom_width_fun(t):
# Note: Custom width/offset functions MUST be vectorizable--you must be able
# to call them with an array input like my_custom_width_fun([0, 0.1, 0.2, 0.3, 0.4])
num_periods = 5
w = 3 + np.cos(2 * np.pi * t * num_periods)
return w
# Create the Path
P = gf.path.straight(length=40)
# Create two cross-sections: one fixed width, one modulated by my_custom_offset_fun
X = gf.CrossSection()
X.add(width=3, offset=-6, layer=(2, 0))
X.add(width=my_custom_width_fun, offset=0, layer=(1, 0))
# Extrude the Path to create the Component
c = gf.path.extrude(P, cross_section=X)
c.plot()
[38]:
We can do the same thing with the offset argument:
[39]:
def my_custom_offset_fun(t):
# Note: Custom width/offset functions MUST be vectorizable--you must be able
# to call them with an array input like my_custom_offset_fun([0, 0.1, 0.2, 0.3, 0.4])
num_periods = 3
w = 3 + np.cos(2 * np.pi * t * num_periods)
return w
# Create the Path
P = gf.path.straight(length=40)
# Create two cross-sections: one fixed offset, one modulated by my_custom_offset_fun
X = gf.CrossSection()
X.add(width=1, offset=my_custom_offset_fun, layer=(2, 0))
X.add(width=1, offset=0, layer=(1, 0))
# Extrude the Path to create the Component
c = gf.path.extrude(P, cross_section=X)
c.plot()
[39]:
Offsetting a Path#
Sometimes it’s convenient to start with a simple Path and offset the line it follows to suit your needs (without using a custom-offset CrossSection). Here, we start with two copies of simple straight Path and use the offset() function to directly modify each Path.
[40]:
def my_custom_offset_fun(t):
# Note: Custom width/offset functions MUST be vectorizable--you must be able
# to call them with an array input like my_custom_offset_fun([0, 0.1, 0.2, 0.3, 0.4])
num_periods = 1
w = 2 + np.cos(2 * np.pi * t * num_periods)
return w
P1 = gf.path.straight(length=40)
P2 = P1.copy() # Make a copy of the Path
P1.offset(offset=my_custom_offset_fun)
P2.offset(offset=my_custom_offset_fun)
P2.mirror((1, 0)) # reflect across X-axis
gf.plot([P1, P2])
Modifying a CrossSection#
In case you need to modify the CrossSection, it can be done simply by specifying a name argument for the cross-sectional element you want to modify later. Here is an example where we name one of thee cross-sectional elements 'myelement1' and 'myelement2':
[41]:
# Create the Path
P = gf.path.arc(radius=10, angle=45)
# Create two cross-sections: one fixed width, one modulated by my_custom_offset_fun
X = gf.CrossSection()
X.add(width=1, offset=0, layer=(1,0), ports=("o1", "o2"), name="myelement1")
X.add(width=1, offset=3, layer=(2,0), name="myelement2")
c = gf.path.extrude(P, X)
c.plot()
[41]:
In case we want to change any of the CrossSection elements, we simply access the Python dictionary that specifies that element and modify the values
[42]:
# Copy our original CrossSection
Xcopy = X.copy()
# Modify
Xcopy["myelement2"]["width"] = 2 # X['myelement2'] is a dictionary
Xcopy["myelement2"]["layer"] = 1 # X['myelement2'] is a dictionary
# Extrude the Path to create the Device
c = gf.path.extrude(P, cross_section=Xcopy)
c.plot()
[42]:
[43]:
import gdsfactory as gf
X1 = gf.CrossSection()
X1.add(width=1.2, offset=0, layer=2, name="wg", ports=("o1", "o2"))
X1.add(width=2.2, offset=0, layer=3, name="etch")
X1.add(width=1.1, offset=3, layer=1, name="wg2")
# Create the second CrossSection that we want to transition to
X2 = gf.CrossSection()
X2.add(width=1, offset=0, layer=2, name="wg", ports=("o1", "o2"))
X2.add(width=3.5, offset=0, layer=3, name="etch")
X2.add(width=3, offset=5, layer=1, name="wg2")
Xtrans = gf.path.transition(cross_section1=X1, cross_section2=X2, width_type="sine")
P1 = gf.path.straight(length=5)
P2 = gf.path.straight(length=5)
wg1 = gf.path.extrude(P1, X1)
wg2 = gf.path.extrude(P2, X2)
P4 = gf.path.euler(radius=25, angle=45, p=0.5, use_eff=False)
wg_trans = gf.path.extrude(P4, Xtrans)
# WG_trans = P4.extrude(Xtrans)
c = gf.Component()
wg1_ref = c << wg1
wg2_ref = c << wg2
wgt_ref = c << wg_trans
wgt_ref.connect("o1", wg1_ref.ports["o2"])
wg2_ref.connect("o1", wgt_ref.ports["o2"])
c.plot()
[43]:
[44]:
len(c.references)
[44]:
3
Note
Any unamed section in the CrossSection won’t be transitioned.
If you don’t add any named sections in a cross-section it will give you an error when making a transition
[45]:
import gdsfactory as gf
import numpy as np
P = gf.Path()
P.append(gf.path.arc(radius=10, angle=90)) # Circular arc
P.append(gf.path.straight(length=10)) # Straight section
P.append(gf.path.euler(radius=3, angle=-90)) # Euler bend (aka "racetrack" curve)
P.append(gf.path.straight(length=40))
P.append(gf.path.arc(radius=8, angle=-45))
P.append(gf.path.straight(length=10))
P.append(gf.path.arc(radius=8, angle=45))
P.append(gf.path.straight(length=10))
gf.plot(P)
[46]:
X = gf.CrossSection()
X.add(width=1, offset=0, layer=(2,0))
c = gf.path.extrude(P, X)
c.plot()
[46]:
[47]:
X2 = gf.CrossSection()
X2.add(width=2, offset=0, layer=(2,0))
c = gf.path.extrude(P, X2)
c.plot()
[47]:
For example this will give you an error
T = gf.path.transition(X, X2)
Solution
[48]:
P = gf.path.straight(length=10, npoints=101)
X = gf.CrossSection()
X.add(width=1, offset=0, layer=gf.LAYER.WG, name="core", ports=("o1", "o2"))
X.add(width=3, offset=0, layer=gf.LAYER.SLAB90)
c = gf.path.extrude(P, X)
c.plot()
[48]:
[49]:
X2 = gf.CrossSection()
X2.add(width=3, offset=0, layer=gf.LAYER.WG, name="core", ports=("o1", "o2"))
c2 = gf.path.extrude(P, X2)
c2.plot()
[49]:
[50]:
T = gf.path.transition(X, X2)
c3 = gf.path.extrude(P, T)
c3.plot()
[50]:
[51]:
c4 = gf.Component()
[52]:
start_ref = c4 << c
trans_ref = c4 << c3
end_ref = c4 << c2
trans_ref.connect("o1", start_ref.ports["o2"])
end_ref.connect("o1", trans_ref.ports["o2"])
[52]:
DeviceReference (parent Device "extrude_134e6fc7", ports ['o1', 'o2'], origin [20. 0.], rotation 0, x_reflection False)
[53]:
c4.plot()
[53]:
cross-section#
You can create functions that return a cross_section in 2 ways:
gf.partialcan customize an existing cross-section for examplegf.cross_section.stripdefine a function that returns a cross_section
[54]:
import gdsfactory as gf
from gdsfactory.tech import Section
[55]:
pin = gf.partial(
gf.cross_section.strip,
layer=(2, 0),
sections=(
Section(layer=gf.LAYER.P, width=2, offset=+2),
Section(layer=gf.LAYER.N, width=2, offset=-2),
),
)
[56]:
c = gf.components.straight(cross_section=pin)
c.plot()
[56]:
[57]:
pin10 = gf.components.straight(cross_section=pin, length=10)
pin10.plot()
[57]:
finally, you can also pass most components Dict that define the cross-section
[58]:
gf.components.straight(
layer=(1, 0),
width=0.5,
sections=(
Section(layer=gf.LAYER.P, width=1, offset=+2),
Section(layer=gf.LAYER.N, width=1, offset=-2),
),
)
2022-03-01 18:39:55.343 | INFO | gdsfactory.show:show:36 - Klayout show straight_ff974c9e: uid 35, ports ['o1', 'o2'], aliases [], 3 polygons, 0 references
straight_ff974c9e: uid 35, ports ['o1', 'o2'], aliases [], 3 polygons, 0 references
[58]:
straight_ff974c9e: uid 35, ports ['o1', 'o2'], aliases [], 3 polygons, 0 references
[59]:
import numpy as np
import gdsfactory as gf
# Create our first CrossSection
X1 = gf.CrossSection()
X1.add(width=0.5, offset=0, layer=1, name="wg", ports=("o1", "o2"))
X1.add(width=0.2, offset=0, layer=3, name="slab")
# Create the second CrossSection that we want to transition to
X2 = gf.CrossSection()
X2.add(width=0.5, offset=0, layer=1, name="wg", ports=("o1", "o2"))
X2.add(width=3.0, offset=0, layer=3, name="slab")
# To show the cross-sections, let's create two Paths and
# create Devices by extruding them
P1 = gf.path.straight(length=5)
P2 = gf.path.straight(length=5)
wg1 = gf.path.extrude(P1, X1)
wg2 = gf.path.extrude(P2, X2)
# Place both cross-section Devices and quickplot them
c = gf.Component()
wg1ref = c << wg1
wg2ref = c << wg2
wg2ref.movex(7.5)
# Create the transitional CrossSection
Xtrans = gf.path.transition(cross_section1=X1, cross_section2=X2, width_type="linear")
# Create a Path for the transitional CrossSection to follow
P3 = gf.path.straight(length=15, npoints=100)
# Use the transitional CrossSection to create a Component
straight_transition = gf.path.extrude(P3, Xtrans)
straight_transition.plot()
[59]:
[60]:
s = gf.export.to_3d(straight_transition, layer_set=gf.layers.LAYER_SET)
s.show()
/usr/share/miniconda/envs/anaconda-client-env/lib/python3.9/site-packages/IPython/core/display.py:419: UserWarning: Consider using IPython.display.IFrame instead
warnings.warn("Consider using IPython.display.IFrame instead")
[60]:
[ ]: